/*
 * file name:module_fs.c
 * version :v1.0.0
 * Change Logs:
 * Date             Author          main change
 * 2024-08-21       lanxin          the first version
 * 2024-09-08       lanxin          add copy delete get function
 * 2025-01-21       lanxin          change para judge logic add ENABLE_REMOUNT
 *                                  fix copy_dir_file
 *
*/

/********************************************* READ ME BEGIN *************************************************************/

/* Brief:
 *
 * *1.RTT 文件系统封装接口。
 * 
 * - fs_mount_xx  | fs_unmount | 挂载卸载文件系统
 * - fs_write_xx  | fs_read_xx | 读写文件
 * - fs_get_xx    | fs_set_xx  | 获取信息，如文件大小、是否存在等。
 * - fs_create_xx | fs_delete_xx | 创建与删除文件和目录
 * - fs_copy_xx					 | 拷贝文件和目录
 * - fs_check_write_data		 | 对写入数据进行回读检查
 * - fs_lock | fs_unlock		 | 操作文件的临界区保护
 * 
 */

/* Notice:
 *
 * *1.是否在挂载失败后进行格式化由 ENABLE_REMOUNT 控制，谨慎选择，格式化之后该设备之前的所有信息都没了。
 *
 * *2.使用前应调用 fs_prepare 进行互斥锁创建，如果使用了 FAL 在 fs_prepare 会调用 fal_init()。
 * 
 */

/********************************************* READ ME END ***************************************************************/

/* source file */
#include "rtthread.h"
#include "module_fs.h"

/* private define */
#define FS_LOCK()                                   if(mutex_fs) rt_mutex_take(mutex_fs,RT_WAITING_FOREVER)
#define FS_UNLOCK()                                 if(mutex_fs) rt_mutex_release(mutex_fs)
#define FS_ASSERT(PARA)                             if((PARA) == 0) { rt_kprintf("file:%s",__FILE__);RT_ASSERT(0);}
#define FS_PARA_CHECK_NULL(PARA)                    if((PARA) == 0) {rt_kprintf("file:%s line:%d para is null\r\n",__FILE__,__LINE__); return;}
#define FS_PARA_CHECK_NULL_THEN_RETURN(PARA,VALUE)  if((PARA) == 0) {rt_kprintf("file:%s line:%d para is null\r\n",__FILE__,__LINE__); return VALUE;}
#define FS_COMPARE(A,B) 							( rt_strcmp((A),(B)) == 0 )

/* variable declaration */
static rt_mutex_t mutex_fs = RT_NULL;
static fs_event_cb_t fs_cb = RT_NULL;
static int file_cnt = 0;
static uint8_t check_fifo[Max_ReadBack_Size_OneTime];

/* function declaration */

static int fs_cmd ( int argc, char *argv[] )
{
#define SHOW_CMD(CMD) rt_kprintf("%s",CMD)

	char mkfs[]             = "fs mkfs cmd                         :fs_cmd mkfs data elm                        [dev fstype]\r\n";
	char delete_file[]      = "fs delete file cmd                  :fs_cmd delete file /sd/data/test.txt        [file path]\r\n";
	char delete_dir[]       = "fs delete dir and file cmd          :fs_cmd delete dir /sd/data yes              [delete dir]\r\n";
	char delete_dir_file[]  = "fs delete dir and file cmd          :fs_cmd delete dir /sd/data no               [not delete dir]\r\n";
	char copy_file[]        = "fs copy file cmd                    :fs_cmd copy file /config /sd syscfg.json    [dstdir srcdir filename]\r\n";
	char copy_dir[]         = "fs copy dir file cmd                :fs_cmd copy dir /config /sd/config          [dstdir srcdir]\r\n";
	char usage[]            = "fs show usage cmd                   :fs_cmd usage /sd                            [path]\r\n";

	if ( FS_COMPARE ( argv[1], "mkfs" ) )
	{
		if ( argc == 4 )
			return fs_mkfs ( argv[2], argv[3] );
		else
			goto __fs_cmd;
	}
	else if ( FS_COMPARE ( argv[1], "delete" ) )
	{
		if ( argc == 4 && FS_COMPARE ( argv[2], "file" ) )
		{
			return fs_delete_file ( argv[3] );
		}
		else if ( argc == 5 && FS_COMPARE ( argv[2], "dir" ) )
		{
			uint8_t del_dir = ( FS_COMPARE ( argv[4], "yes" ) ) ? 1 : 0;
			return fs_delete_dir_file ( argv[3], del_dir );
		}
		else
			goto __fs_cmd;
	}
	else if ( FS_COMPARE ( argv[1], "copy" ) )
	{
		if ( argc == 6 && FS_COMPARE ( argv[2], "file" ) )
		{
			return fs_copy_file ( argv[3], argv[4], argv[5] );
		}
		else if ( argc == 5 && FS_COMPARE ( argv[2], "dir" ) )
		{
			return fs_copy_dir_file ( argv[3], argv[4] );
		}
		else
			goto __fs_cmd;
	}
	else if ( FS_COMPARE ( argv[1], "usage" ) )
	{
		if ( argc == 3 )
		{
			int usage = fs_get_capacity ( argv[2] );
			rt_kprintf ( "%s usage is %d\r\n", argv[2], usage );
			return 0;
		}
	}
	else if ( FS_COMPARE ( argv[1], "w_r" ) )
	{
		char *w_r = "write read test\r\n";
		char r_buf[32];

		rt_kprintf ( "write:%s", w_r );
		fs_write_with_create ( "/test/hello.txt", 0, w_r, rt_strlen ( w_r ) );
		fs_read ( "/test/hello.txt", 0, r_buf, rt_strlen ( w_r ) );
		fs_delete_file ( "/test/hello.txt" );
		rt_kprintf ( "read:%s", r_buf );

		return 0;
	}

__fs_cmd:
	SHOW_CMD ( mkfs );
	SHOW_CMD ( delete_file );
	SHOW_CMD ( delete_dir_file );
	SHOW_CMD ( delete_dir );
	SHOW_CMD ( copy_dir );
	SHOW_CMD ( copy_file );
	SHOW_CMD ( usage );

	return 0;
}

int fs_lock ( uint32_t time )
{
	if ( mutex_fs )
		return rt_mutex_take ( mutex_fs, time );

	return RT_ERROR;
}

int fs_unlock ( void )
{
	if ( mutex_fs )
		return rt_mutex_release ( mutex_fs );

	return RT_ERROR;
}

static void do_fs_cb ( int event, void *para )
{
	if ( fs_cb )
		fs_cb ( event, para );
}

//删除目录下所有文件及该目录 该函数由于有递归调用，所以无互斥保护
//0 成功 其他失败
static int rmdir_and_all_file_under_dir ( const char *pathname )
{
	DIR *dir = RT_NULL;
	struct dirent *dirent = RT_NULL;
	char path[300];

	FS_PARA_CHECK_NULL_THEN_RETURN ( pathname, -1 );

	/* 打开目录 */
	dir = opendir ( pathname );

	/* 每读取一次目录，目录流的指针位置将自动往后递推 1 个位置。*/
	/* 借此遍历所有的文件或目录 */
	while ( ( dirent = readdir ( dir ) ) != RT_NULL )
	{
		/* 如果当前目录项名字为"."或".."，则跳过该项 */
		if ( rt_strcmp ( ".", dirent->d_name ) != 0 && rt_strcmp ( "..", dirent->d_name ) != 0 )
		{
			rt_memset ( path, 0, 300 );

			/* 获取该文件的路径：目录+文件名 */
			rt_snprintf ( path, 300, "%s/%s", pathname, dirent->d_name );

			/* 是文件则直接删除 */
			if ( dirent->d_type == DT_REG )
			{
				if ( unlink ( path ) != 0 )
					rt_kprintf ( "cannot remove %s\r\n", path );
				else
					rt_kprintf ( "delete file success [%s]\r\n", path );
			}
			/* 目录则再次执行本函数 删除该目录下的文件 */
			else if ( dirent->d_type == DT_DIR )
				rmdir_and_all_file_under_dir ( path );
		}
	}

	/* 关闭目录 */
	closedir ( dir );

	/* 只有目录下没有文件或目录的才可以删除 */
	if ( rmdir ( pathname ) != 0 )
	{
		rt_kprintf ( "cannot remove dir %s\r\n", pathname );
		return -1;
	}

	rt_kprintf ( "delete dir success [%s]\r\n", pathname );

	return 0;
}

static int get_file_cnt ( const char *dirpath )
{
	DIR *dir = NULL;
	struct dirent *dirent = NULL;
	char path[300];

	if ( ( dir = opendir ( dirpath ) ) == NULL )
	{
		rt_kprintf ( "cannot opendir %s\r\n", dirpath );
		return -1;
	}

	while ( 1 )
	{
		dirent = readdir ( dir );

		if ( dirent == NULL )
			break;

		if ( strcmp ( ".", dirent->d_name ) != 0 && strcmp ( "..", dirent->d_name ) != 0 )
		{
			rt_snprintf ( path, 300, "%s/%s", dirpath, dirent->d_name );

			if ( dirent->d_type == DT_REG )
			{
				file_cnt++;
			}
			else if ( dirent->d_type == DT_DIR )
			{
				get_file_cnt ( path );
			}
		}
	}

	closedir ( dir );

	return file_cnt;
}

//拷贝文件
static int copy_file ( const char *dst_dir, const char *src_dir, const char *file_name )
{
	char src_file_path[128];
	char dst_file_path[128];
	int _file_src_fd;
	int _file_dst_fd;

	FS_PARA_CHECK_NULL_THEN_RETURN ( dst_dir, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( src_dir, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( file_name, -1 );

	rt_sprintf ( src_file_path, "%s/%s", src_dir, file_name );
	rt_sprintf ( dst_file_path, "%s/%s", dst_dir, file_name );

	/* 检查源文件是否可以访问 */
	if ( access ( src_file_path, F_OK )  != 0 )
	{
		rt_kprintf ( "copy file:src file [%s] can not access\r\n", src_file_path );
		return -1;
	}

	/* 检查是否目标目录存在 */
	if ( fs_get_dir_exist ( dst_dir ) <= 0 )
	{
		int dir_res = fs_create_dir ( dst_dir );
		rt_kprintf ( "copy file:create dst dir [%s] %s\r\n", dst_dir, dir_res == 0 ? "ok" : "failed" );
	}

	/* 目标路径是否存在源文件，是则删除 */
	if ( access ( dst_file_path, F_OK ) == 0 )
	{
		unlink ( dst_file_path );
		rt_kprintf ( "copy file:unlink dst src file [%s]\r\n", dst_file_path );
	}

	/* 打开文件 */
	_file_src_fd = open ( src_file_path, O_RDONLY );
	_file_dst_fd = open ( dst_file_path, O_RDWR | O_CREAT );

	if ( _file_src_fd <= 0 || _file_dst_fd <= 0 )
	{
		rt_kprintf ( "copy file %s failed ,file open failed\r\n", src_file_path );
		return -1;
	}

	/* 开辟缓存空间 */
	uint8_t *_p_buf_temp = ( uint8_t * ) rt_malloc ( Max_Copy_File_FIFO_SIZE );

	if ( _p_buf_temp == 0 )
	{
		close ( _file_src_fd );
		close ( _file_dst_fd );

		return -1;
	}

	ssize_t _src_read_size = 0 ;

	/* 拷贝数据 */
	while ( ( _src_read_size = read ( _file_src_fd, _p_buf_temp, Max_Copy_File_FIFO_SIZE ) ) )
	{
		write ( _file_dst_fd, _p_buf_temp, _src_read_size );
		_src_read_size = 0;
		rt_memset ( _p_buf_temp, 0, Max_Copy_File_FIFO_SIZE );
	}

	close ( _file_src_fd );
	close ( _file_dst_fd );
	rt_free ( _p_buf_temp );
	rt_kprintf ( "copy file:%s to %s down\r\n", src_file_path, dst_dir );

	return 0;
}

//拷贝目录 src 下的所有文件和目录到目录 dst 下
static int copy_dir_file ( const char *dst_dir, const char *src_dir )
{
	FS_ASSERT ( dst_dir );
	FS_ASSERT ( src_dir );

	DIR *_dir = opendir ( src_dir );

	if ( _dir == 0 )
	{
		rt_kprintf ( "cannot open copy src dir %s\r\n", src_dir );
		return -1;
	}

	char new_src_dir[128];
	char new_dst_dir[128];
	struct dirent *_dirent = 0;

	while ( ( _dirent = readdir ( _dir ) ) != RT_NULL )
	{
		/* 如果当前目录项名字为"."或".."，则跳过该项 */
		if ( rt_strcmp ( ".", _dirent->d_name ) != 0 && rt_strcmp ( "..", _dirent->d_name ) != 0 )
		{
			rt_memset ( new_src_dir, 0, 128 );
			rt_memset ( new_dst_dir, 0, 128 );

			/* 是文件则直接拷贝 */
			if ( _dirent->d_type == DT_REG )
				copy_file ( dst_dir, src_dir, _dirent->d_name );
			/* 是目录则再次执行本函数 拷贝该目录下的文件 */
			else if ( _dirent->d_type == DT_DIR )
			{
				/* 获取该文件的路径：目录+文件名 */
				if ( rt_strcmp ( src_dir, "/" ) == 0 )
					rt_sprintf ( new_src_dir, "/%s", _dirent->d_name );
				else
					rt_sprintf ( new_src_dir, "%s/%s", src_dir, _dirent->d_name );

				if ( rt_strcmp ( dst_dir, "/" ) == 0 )
					rt_sprintf ( new_dst_dir, "/%s", _dirent->d_name );
				else
					rt_sprintf ( new_dst_dir, "%s/%s", dst_dir, _dirent->d_name );

				if ( access ( new_dst_dir, F_OK ) != 0 )
					mkdir ( new_dst_dir, 0x777 );

				copy_dir_file ( new_dst_dir, new_src_dir );
			}
		}
	}

	closedir ( _dir );

	return 0;
}

//挂载设备 fs_mount "sd0" "/" "elm"
int fs_mount ( const char *dev_name, const char *path, const char *fs_type )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( dev_name, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( fs_type, -1 );

	if ( dfs_mount ( dev_name, path, fs_type, 0, 0 ) == 0 )
	{
		rt_kprintf ( "mount filesystem at [%s] fs_name:[%s] ok\r\n", path, fs_type );
		return 0;
	}

#ifdef ENABLE_REMOUNT
	else
	{
		/* 格式化 */
		dfs_mkfs ( fs_type, dev_name );

		/* 再次挂载 */
		if ( dfs_mount ( dev_name, path, fs_type, 0, 0 ) == RT_EOK )
		{
			rt_kprintf ( "mount filesystem at [%s] fs_name:[%s] ok\r\n", path, fs_type );
			return 0;
		}
		else
		{
			rt_kprintf ( "dev %s mount %s file system  to %s failed\r\n", dev_name, fs_type, path );

			return -1;
		}
	}

#else
	rt_kprintf ( "dev %s mount %s file system  to %s failed\r\n", dev_name, fs_type, path );
#endif/* ENABLE_REMOUNT */

	return -1;
}

#ifdef ENABLE_ROM_FS_API
//在 rom 上挂载只读文件系统
int fs_mount_at_rom ( const char *path, const void *data )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( data, -1 );

	int res = dfs_mount ( 0, path, "rom", 0, data );
	rt_kprintf ( "mount filesystem at [%s] fs_name:[rom] %s\r\n", path, res == 0 ? "ok" : "failed" );
	return res;
}
#endif

#ifdef ENABLE_RAM_FS_API
//在一片内存里面挂载文件系统 fs_mount_at_ram("ramdisk","/","elm",512)
//使用的内存为：block_cnt * RAM_DISK_BLOCK_SIZE
int fs_mount_at_ram ( const char *dev_name, const char *path, const char *fs_name, uint32_t size_byte )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( dev_name, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( fs_name, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( size_byte, -1 );

	rt_size_t block_cnt = size_byte / RAM_DISK_BLOCK_SIZE;
	block_cnt = block_cnt ? block_cnt : RAM_DISK_BLOCK_SIZE;

	if ( ramdisk_init ( dev_name, 0, RAM_DISK_BLOCK_SIZE, block_cnt ) != 0 )
	{
		rt_kprintf ( "ramdisk init failed\r\n" );
		return -1;
	}

	if ( dfs_mkfs ( fs_name, dev_name ) != 0 )
	{
		rt_kprintf ( "ramdisk mkfs failed\r\n" );
		return -1;
	}

	if ( fs_mount ( dev_name, path, fs_name ) != 0 )
		return -1;

	return 0;
}
#endif

#ifdef ENABLE_FAL_FS_API
//在 fal 分区上创建块设备
static int fal_flash_block_device_create ( const char *part_name )
{
	/* 查找分区 */
	const struct fal_partition *part_filesystem = fal_partition_find ( part_name );
	( void ) ( part_filesystem );

	/* 创建块设备 */
	if ( fal_blk_device_create ( part_name ) == RT_NULL )
	{
		rt_kprintf ( "block device create on [%s] failed\r\n", part_name );

		return -1;
	}

	return 0;
}

//在fal 分区上挂载文件系统
int fs_mount_at_fal_partition ( const char *part_name, const char *path, const char *fs_type )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( part_name, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( fs_type, -1 );

	if ( fal_flash_block_device_create ( part_name ) != 0 )
		return -1;

	return fs_mount ( part_name, path, fs_type );
}

#endif

//卸载设备 fs_unmount "/sd"
int fs_unmount ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );

	FS_LOCK();
	int res = dfs_unmount ( path );
	FS_UNLOCK();

	rt_kprintf ( "unmount %s %s\r\n", path, res == 0 ? "ok" : "failed" );

	return res;
}

//格式化设备 fs_mkfs "sd0" "elm"
int fs_mkfs ( const char *dev_name, const char *fs_name )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( dev_name, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( fs_name, -1 );

	FS_LOCK();
	int _res = dfs_mkfs ( fs_name, dev_name );
	FS_UNLOCK();
	rt_kprintf ( "mkfs %s %s %s\r\n", dev_name, fs_name, _res == 0 ? "ok" : "failed" );

	return _res;
}

//删除文件 0 成功 其他失败 /sd/data/test.txt
int fs_delete_file ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );

	FS_LOCK();
	int _res = unlink ( path );
	FS_UNLOCK();

	do_fs_cb ( E_FS_EVENT_DELETE_FAILED, ( char * ) path );
	rt_kprintf ( "unlink %s %s\r\n", path, _res == 0 ? "ok" : "failed" );

	return _res;
}

//删除目录 /sd/data del_dir 1 删除该目录及该目录下所有文件 0 删除该目录下所有文件
int fs_delete_dir_file ( const char *path, uint8_t del_dir )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );

	FS_LOCK();
	int _res = rmdir_and_all_file_under_dir ( path );

	if ( _res != 0 )
	{
		rt_kprintf ( "delete dir and under dir file [%s] failed\r\n", path );
		FS_UNLOCK();
		return -1;
	}

	rt_kprintf ( "delete dir and under dir file [%s] down\r\n", path );
	FS_UNLOCK();

	if ( del_dir == 0 && _res == 0 )
		_res = fs_create_dir ( path );

	return _res;
}

//根据目录路径依次创建目录
// /sd/data --> 创建目录 /sd/data
int fs_create_dir ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );

	FS_LOCK();
	int res = 0;
	char _path_change[64];
	rt_sprintf ( _path_change, "%s/gg", path );

	char _dir[64];
	char *_temp = 0;
	char *_path = _path_change + 1;

	for ( ;; )
	{
		_temp = strchr ( _path, '/' );

		if ( _temp )
		{
			rt_memset ( _dir, 0, 64 );
			rt_strncpy ( _dir, _path_change, _temp - _path_change );

			if ( access ( _dir, F_OK ) != 0 )
			{
				if ( mkdir ( _dir, 0x777 ) != 0 )
				{
					res = -1;
					break;
				}
			}

			_path = _temp + 1;
		}
		else
			break;
	}

	FS_UNLOCK();

	rt_kprintf ( "mkdir:%s %s\r\n", path, res == 0 ? "ok" : "failed" );

	return res;
}

//根据文件路径依次创建所需的文件目录
// /sd/data/test.txt --> 创建 /sd/data 目录
int fs_create_dir_by_file_path ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );

	FS_LOCK();

	int res = 0;
	char _dir[64];
	char *_temp = 0;
	const char *_path = path + 1;

	for ( ;; )
	{
		_temp = strchr ( _path, '/' );

		if ( _temp )
		{
			rt_memset ( _dir, 0, 64 );
			rt_strncpy ( _dir, path, _temp - path );

			if ( access ( _dir, F_OK ) != 0 )
			{
				if ( mkdir ( _dir, 0x777 ) != 0 )
				{
					res = -1;
					break;
				}
			}

			_path = _temp + 1;
		}
		else
			break;
	}

	FS_UNLOCK();

	return res;
}

//创建文件，目录不存会先创建目录 fs_create_file /sd/data/20250121.bin
int fs_create_file ( const char *path )
{
	int fd = -1;

	if ( fs_get_file_access ( path ) )
		return 0;
	else
	{
		fs_create_dir_by_file_path ( path );
		fd = open ( path, O_CREAT );

		if ( fd )
		{
			close ( fd );
			return 0;
		}
		else
			return -1;
	}

	do_fs_cb ( E_FS_EVENT_CREATE_FILE_FAILED, ( void * ) path );
	return -1;
}

//向文件指定位置写入数据，文件不存则失败,返回值为 写入的字节数
ssize_t fs_write ( const char *path, ssize_t offset, const void *buf, ssize_t len )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( buf, 0 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( len, 0 );

	FS_LOCK();
	int file_fd = open ( path, O_WRONLY );

	if ( file_fd <= 0 )
		goto _write_err;

	if ( lseek ( file_fd, offset, SEEK_SET ) != offset )
		goto _write_err;

	if ( write ( file_fd, buf, len ) != len )
		goto _write_err;

	close ( file_fd );
	FS_UNLOCK();

	return len;

_write_err:
	close ( file_fd );
	FS_UNLOCK();

	do_fs_cb ( E_FS_EVENT_WRITE_FAILED, ( void * ) path );
	rt_kprintf ( "fs_write %s failed\r\n", path );
	return 0;
}

//向文件指定位置写入数据，文件不存在则创建对应目录及文件
ssize_t fs_write_with_create ( const char *path, ssize_t offset, const void *buf, ssize_t len )
{
	if ( fs_create_file ( path ) != 0 )
		return 0;

	return fs_write ( path, offset, buf, len );
}

//以追加模式向文件写入内容，文件不存在则创建对应目录及文件
ssize_t fs_write_append ( const char *path, const void *buf, ssize_t len )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( buf, -1 );

	int fd = -1;

	if ( fs_create_file ( path ) != 0 )
		return 0;

	FS_LOCK();

	if ( ( fd = open ( path, O_RDWR ) ) < 0 )
		goto _err;

	if ( lseek ( fd, 0, SEEK_END )  == -1 )
		goto _err;

	if ( write ( fd, buf, len ) != len )
		goto _err;

	close ( fd );

	FS_UNLOCK();

	return len;

_err:
	close ( fd );
	FS_UNLOCK();
	do_fs_cb ( E_FS_EVENT_WRITE_FAILED, ( char * ) path );
	rt_kprintf ( "fs_write_append %s failed\r\n", path );
	return 0;
}

//写入文件前清空原文件，并从起始位置开始写入文件不存则创建对应目录及文件
ssize_t fs_write_O_WRONLY_O_TRUNC ( const char *path, const void *buf, ssize_t len )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( buf, -1 );

	if ( fs_create_file ( path ) != 0 )
		return 0;

	FS_LOCK();

	int fd = -1;

	if ( ( fd = open ( path, O_WRONLY | O_TRUNC ) ) < 0 )
		goto _err;

	if ( lseek ( fd, 0, SEEK_SET ) == -1 )
		goto _err;

	if ( write ( fd, buf, len ) != len )
		goto _err;

	close ( fd );

	FS_UNLOCK();

	return len;

_err:
	close ( fd );
	FS_UNLOCK();

	do_fs_cb ( E_FS_EVENT_WRITE_FAILED, ( char * ) path );
	rt_kprintf ( "fs_write_O_WRONLY_O_TRUNC %s failed\r\n", path );
	return 0;
}

//读取文件
ssize_t fs_read ( const char *path, ssize_t offset, void *buf, ssize_t len )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( buf, 0 );

	FS_LOCK();

	int fd = open ( path, O_RDONLY );

	if ( fd < 0 )
		goto _err;

	/* check there is enough data */
	off_t off = lseek ( fd, 0, SEEK_END );

	if ( off < offset + len )
	{
		rt_kprintf ( "read %s size is larger than file size %lu\r\n", path, ( unsigned long ) len );
		goto _err;
	}

	if ( lseek ( fd, offset, SEEK_SET ) != offset )
		goto _err;

	if ( read ( fd, buf, len ) != len )
		goto _err;

	close ( fd );

	FS_UNLOCK();

	return len;

_err:
	close ( fd );
	FS_UNLOCK();

	do_fs_cb ( E_FS_EVENT_READ_FAILED, ( void * ) path );
	rt_kprintf ( "fs_read %s failed\r\n", path );
	return 0;
}

//读取整个文件 大小不超过 Max_Read_Size_OneTime
//返回值为读取的数据大小,调用后都应该判断一下 buf，不为0 则释放掉该内存。
//void *buf = 0; fs_read_all("/sd/test.txt",&buf); if(buf) rt_free(buf);
ssize_t fs_read_all ( const char *path, char **buf )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );

	ssize_t read_size = 0;
	FS_LOCK();

	struct stat _file_state = {.st_size = 0};

	if ( stat ( path, &_file_state ) != 0 )
		goto _read_failed;
	else if ( _file_state.st_size > Max_Read_Size_OneTime )
	{
		rt_kprintf ( "read file [%s] [%d] is too bigger than limit [%d]\r\n",
		             path, _file_state.st_size, Max_Read_Size_OneTime );
		goto _read_failed;
	}

	char *p_read = rt_calloc ( _file_state.st_size + 1, 1 );

	if ( p_read == 0 )
	{
		rt_kprintf ( "there is no memory for read file [path]\r\n" );
		goto _read_failed;
	}

	int fd = open ( path, O_RDONLY );

	if ( fd < 0 )
		goto _read_failed;

	read_size = _file_state.st_size;

	if ( read ( fd, p_read, read_size ) != read_size )
		goto _read_failed;

	close ( fd );
	p_read[_file_state.st_size] = '\0';
	*buf = p_read;

	FS_UNLOCK();

	return read_size;

_read_failed:
	close ( fd );

	if ( p_read )
		rt_free ( p_read );

	FS_UNLOCK();
	rt_kprintf ( "fs_read_all %s failed\r\n", path );
	do_fs_cb ( E_FS_EVENT_READ_FAILED, ( void * ) path );
	return 0;
}

//读取指定位置内容与传入的 p_write 进行比较 0一模一样 -1 有差异
int fs_write_data_check ( const char *path, ssize_t offset, const void *p_write, ssize_t len )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( p_write, -1 );
	FS_PARA_CHECK_NULL_THEN_RETURN ( len, -1 );

	ssize_t c_index = len, r_index = offset, need_size = 0, read_size = 0;
	uint8_t *p_src = ( uint8_t * ) p_write;

	while ( c_index )
	{
		need_size = c_index > Max_ReadBack_Size_OneTime ? Max_ReadBack_Size_OneTime : c_index;
		rt_memset ( check_fifo, 0, Max_ReadBack_Size_OneTime );

		if ( need_size )
		{
			read_size = fs_read ( path, r_index, check_fifo, need_size );

			if ( read_size != need_size )
				break;

			if ( rt_memcmp ( p_src, check_fifo, read_size ) != 0 )
				break;

			c_index -= read_size;
			r_index += read_size;
			p_src += read_size;
		}
		else
			break;
	}

	if ( c_index != 0 )
		do_fs_cb ( E_FS_EVENT_CHECK_FAILED, ( char * ) path );

	return c_index == 0 ? 0 : -1;
}

//拷贝文件 fs_copy_file / /sd test.txt
int fs_copy_file ( const char *dst_dir, const char *src_dir, const char *file_name )
{
	FS_LOCK();
	int res = copy_file ( dst_dir, src_dir, file_name );
	FS_UNLOCK();

	return res;
}

//拷贝目录 src 下的所有文件和目录到目录 dst 下
//fs_copy_dir_file / /sd
int fs_copy_dir_file ( const char *dst_dir, const char *src_dir )
{
	int res = -1;
	FS_LOCK();
	res = copy_dir_file ( dst_dir, src_dir );
	FS_UNLOCK();

	return res;
}

//文件是否存在 1 存在 0 不存在
int fs_get_file_access ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );

	int _res = -1;
	FS_LOCK();
	_res = access ( path, F_OK );
	FS_UNLOCK();
	return _res == 0;
}

//检查目录是否存在 1 存在 0 不存在
int fs_get_dir_exist ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );

	int res = -1;

	FS_LOCK();

	DIR *_dir_temp = opendir ( path );

	if ( _dir_temp )
	{
		closedir ( _dir_temp );
		res = 0;
	}

	FS_UNLOCK();

	return res == 0;
}

//检查目录下是否有文件 1 有文件 0 无文件
int fs_get_dir_file_exist ( const char *path )
{
	int res = -1;

	FS_PARA_CHECK_NULL_THEN_RETURN ( path, -1 );

	FS_LOCK();

	DIR *_dir = opendir ( path );

	if ( _dir == 0 )
	{
		FS_UNLOCK();
		return -1;
	}

	struct dirent *_dirent = readdir ( _dir );

	if ( _dirent )
		res = 0;

	closedir ( _dir );

	FS_UNLOCK();

	return res == 0;
}

//获取某个路径得使用率 返回值为0-100
int fs_get_capacity ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );

	struct statfs state;

	if ( dfs_statfs ( path, &state ) < 0 )
		return -1;

	return ( int ) ( ( ( float ) state.f_blocks - ( float ) state.f_bfree ) / ( float ) state.f_blocks * 100 + 0.5 );
}

//获取文件大小
ssize_t fs_get_file_size ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );

	FS_LOCK();
	struct stat _file_state = {.st_size = 0};
	stat ( path, &_file_state );
	FS_UNLOCK();

	return _file_state.st_size;
}

//获取某个路径下有多少个文件
int fs_get_dir_file_num ( const char *path )
{
	FS_PARA_CHECK_NULL_THEN_RETURN ( path, 0 );
	file_cnt = 0;
	FS_LOCK();
	file_cnt = get_file_cnt ( path );
	FS_UNLOCK();

	return file_cnt;
}

//设置文件系统回调函数
void fs_set_event_cb ( fs_event_cb_t cb )
{
	FS_LOCK();
	fs_cb = cb;
	FS_UNLOCK();
}

void fs_clear_event_cb ( void )
{
	FS_LOCK();
	fs_cb = 0;
	FS_UNLOCK();
}

int fs_prepare ( void )
{
	mutex_fs = rt_mutex_create ( "fs_lock", RT_IPC_FLAG_FIFO );
	FS_ASSERT ( mutex_fs );

#ifdef ENABLE_FAL_FS_API
	fal_init();
#endif
	return 0;
}

/* 导出到初始化列表 */

/* 导出到 MSH 命令列表 */
MSH_CMD_EXPORT ( fs_cmd, fs_cmd );
